 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "expression.h"
#include "function.h"
#include "symtab.h"

struct expression* create_zero_expression()
{
    struct expression* res = (struct expression*)malloc(sizeof(struct expression));
    res->type = T_UNDEFINED;
    res->first = NULL;
    res->second = NULL;
    res->third = NULL;
    res->oper = O_DUMMY;
    res->value = 0;
    res->name = '\0';
    return res;
}

struct expression* create_constanta(double vl)
{
    struct expression* res = create_zero_expression();
    res->type = T_CONSTANTA;
    res->value = vl;
    return res;
}

struct expression* create_variable(const char* nm)
{
    struct expression* res = create_zero_expression();
    res->type = T_VARIABLE;
    res->name = (char*)malloc(strlen(nm)+1);
    strcpy(res->name, nm);
    return res;
}

struct expression* create_unary(enum operation op, struct expression* ex)
{
    struct expression* res = create_zero_expression();
    res->type = T_UNARY;
    res->oper = op;
    res->first = ex;
    return res;
}

struct expression* create_binary(enum operation op, struct expression* ex0, struct expression* ex1)
{
    struct expression* res = create_zero_expression();
    res->type = T_BINARY;
    res->oper = op;
    res->first = ex0;
    res->second = ex1;
    return res;
}

struct expression* create_funcall_expression(struct funcall* fc)
{
    struct expression* res = create_zero_expression();
    res->type = T_APPLY;
    res->fcall = fc;
    return res;
}

struct expression* create_branching_expression(struct expression* exif, struct expression* exthen, struct expression* exelse)
{
    struct expression* res = create_zero_expression();
    res->type = T_BRANCHING;
    res->first = exif;
    res->second = exthen;
    res->third = exelse;
    return res;
}


struct expression* create_input_expression()
{
    struct expression* res = create_zero_expression();
    res->type = T_INPUT;
    return res;
}

struct expression* create_print_expression(struct expression* expr)
{
    struct expression* res = create_zero_expression();
    res->type = T_PRINT;
    res->first = expr;
    return res;
}

void destroy_expression(struct expression* ex)
{
    switch( ex->type ) {
        case T_CONSTANTA:
            break;
        case T_VARIABLE:
            free(ex->name);
            break;
        case T_UNARY:
            destroy_expression(ex->first);
            break;
        case T_BINARY:
            destroy_expression(ex->first);
            destroy_expression(ex->second);
            break;
        case T_APPLY:
            destroy_funcall(ex->fcall);
            break;
    }
    free(ex);
}

double evaluate(struct expression* expr, struct symbol_table* env)
{
    // for safety, but maybe semantically not correct 
    if( expr == NULL ) return 0;
    
    // the constant evaluates itself 
    if( T_CONSTANTA == expr->type )
            return expr->value;

    // take value of variable from symbol table
    if( T_VARIABLE == expr->type ) {
        struct symbol* sym = search_symbol(env, expr->name);
        if( sym == NULL ) return 0;
        return evaluate(sym->value.expr, env);
    }
    
    // evaluate expression then apply operation
    if( T_UNARY == expr->type ) {
        double result = evaluate(expr->first, env);
        if( expr->oper == O_SUB )
            result = -result;
        else if( expr->oper == O_NOT )
            result = !result;
        return result;
    }
    
    // evaluate left and right parts and then apply operation
    if( T_BINARY == expr->type ) {
        double temp0 = evaluate(expr->first, env);
        double temp1 = evaluate(expr->second, env);
        double result = 0;
        switch( expr->oper ) {
            case O_ADD: result = temp0 + temp1;  break;
            case O_SUB: result = temp0 - temp1;  break;
            case O_MUL: result = temp0 * temp1;  break;
            case O_DIV: result = temp0 / temp1;  break;
            case O_MOD: result = (double)((long int)temp0 % (long int)temp1);  break;
            case O_EQ:  result = temp0 == temp1; break;
            case O_NE:  result = temp0 != temp1; break;
            case O_GT:  result = temp0 > temp1;  break;
            case O_GE:  result = temp0 >= temp1; break;
            case O_LT:  result = temp0 < temp1;  break;
            case O_AND: result = temp0 && temp1; break;
            case O_OR:  result = temp0 || temp1; break;
        }
        return result;
    }
    
    // IF first THEN second ELSE third
    if( T_BRANCHING == expr->type ) {
        if( 0 != evaluate(expr->first, env) )
            return evaluate(expr->second, env);
        return evaluate(expr->third, env);
    }
    
    // input and print
    if( T_INPUT == expr->type ) {
        double result = 0;
        scanf("%lf", &result);
        return result;
    }
    if( T_PRINT == expr->type ) {
        double result = evaluate(expr->first, env);
        printf("%lf\n", result);
        return result;
    }
    
    // apply function to arguments
    if( T_APPLY == expr->type ) 
        return evaluate_funcall(expr->fcall, env);

    return 0;
}


/**/
void print_expression(struct expression* expr)
{
    switch( expr->type ) {
        case T_CONSTANTA:
            printf("%lf", expr->value);
            break;
        case T_VARIABLE:
            printf("%s", expr->name);
            break;
        case T_UNARY:
            printf("%d", expr->oper);
            print_expression(expr->first);
            break;
        case T_BINARY:
            printf("%d", expr->oper);
            print_expression(expr->first);
            print_expression(expr->second);
            break;
        case T_BRANCHING:
            printf("if(");
            print_expression(expr->first);
            print_expression(expr->second);
            print_expression(expr->third);
            printf(")");
            break;
        case T_APPLY:
            printf("apply(%s|", expr->fcall->func);
            for(int i = 0; i < expr->fcall->argc; ++i)
                print_expression(expr->fcall->args[i]);
            printf(")");
    }
}
